MonoGame笔记(三)Event-driven Input

XNA中Input的检测是放在Update方法中的, 也就是说每一帧都在轮询输入设备

1
2
3
4
5
6
7
8
9
KeyboardState keyboardState = Keyboard.GetState( );
if (keyboardState.IsKeyDown(Keys.Left))
ringsPosition.X -= ringsSpeed;
if (keyboardState.IsKeyDown(Keys.Right))
ringsPosition.X += ringsSpeed;
if (keyboardState.IsKeyDown(Keys.Up))
ringsPosition.Y -= ringsSpeed;
if (keyboardState.IsKeyDown(Keys.Down))
ringsPosition.Y += ringsSpeed;

这种方式跟Flash或者U3D的使用经验不同, 也和WPF/Winform的开发方式不同.
后者的使用方式类似btn.OnClick += ClickHanlder/btn.addClickListener(ClickHandler)/
btn.addListener(Mouse.Click, ClickHandler)

GitHub上有一个MonoGame-EventDriven-Input项目
https://github.com/ClassicThunder/MonoGame-EventDriven-Input

作者提到轮询方式的缺点:

XNA has a rather severe limitation with regards to handling keyboard input. It is unable detect any keystrokes that occur between pollings. Because of this if your game is updating at the default 60 fps there are 0.017 seconds between polls can be too slow for individual characters of memorized sequences such as “ing”. This method also has the advantage of behaving consistently with other applications, the double click time, character repeat time, etc… are all controlled by your windows settings.

两帧之间的Input检测不到

那它是如何做到Event-driven的? 以下是使用方式

1
2
3
4
5
6
7
8
9
Input _eventDrivenInput;
_eventDrivenInput = new MonoGameInput(this);

_eventDrivenInput.Update(gameTime);

_eventDrivenInput.KeyDown += (sender, keyDown) =>
{
//Subscribe to the events
};

Using the library is very simple. Simple create an instance of the MonoGameInput object, call Update in the Game.Update function, and then subscribe to the events.

在Game.Update中调用_eventDrivenInput.Update(gameTime);

看MonoGameInput的Update

1
2
3
4
5
6
7
8
9
10
11
12
13
public sealed class MonoGameInput : Input
{
private readonly MonoGameMouseEvents _mouseEvents;
private readonly MonoGameKeyboardEvents _monoGameKeyboardEvents;
private readonly MonoGameTouchEvents _monoGameTouchEvents;

override public void Update(GameTime gameTime)
{
_mouseEvents.Update(gameTime);
_monoGameKeyboardEvents.Update(gameTime);
_monoGameTouchEvents.Update(gameTime);
}
}

再看MonoGameMouseEvents的Update

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
internal class MonoGameMouseEvents
{
internal event EventHandler<MouseEventArgs> ButtonReleased;
internal event EventHandler<MouseEventArgs> ButtonPressed;
internal event EventHandler<MouseEventArgs> ButtonDoubleClicked;
internal event EventHandler<MouseEventArgs> MouseMoved;
internal event EventHandler<MouseEventArgs> MouseWheelMoved;

private MouseState _previous;
private MonoGameMouseEventArgs _lastClick;

internal void Update(GameTime gameTime)
{
var current = Mouse.GetState();

// Check button press events.
if (current.LeftButton == ButtonState.Pressed
&& _previous.LeftButton == ButtonState.Released)
{
OnButtonPressed(this, new MonoGameMouseEventArgs(
current.X,
current.Y,
gameTime.TotalGameTime,
_previous,
current,
MouseButton.Left));
}

if (current.MiddleButton == ButtonState.Pressed
&& _previous.MiddleButton == ButtonState.Released)
{
OnButtonPressed(this, new MonoGameMouseEventArgs(
current.X,
current.Y,
gameTime.TotalGameTime,
_previous,
current,
MouseButton.Middle));
}

if (current.RightButton == ButtonState.Pressed
&& _previous.RightButton == ButtonState.Released)
{
OnButtonPressed(this, new MonoGameMouseEventArgs(
current.X,
current.Y,
gameTime.TotalGameTime,
_previous,
current,
MouseButton.Right));
}

if (current.XButton1 == ButtonState.Pressed
&& _previous.XButton1 == ButtonState.Released)
{
OnButtonPressed(this, new MonoGameMouseEventArgs(
current.X,
current.Y,
gameTime.TotalGameTime,
_previous,
current,
MouseButton.XButton1));
}

if (current.XButton2 == ButtonState.Pressed
&& _previous.XButton2 == ButtonState.Released)
{
OnButtonPressed(this, new MonoGameMouseEventArgs(
current.X,
current.Y,
gameTime.TotalGameTime,
_previous,
current,
MouseButton.XButton2));
}

// Check button releases.
if (current.LeftButton == ButtonState.Released
&& _previous.LeftButton == ButtonState.Pressed)
{
OnButtonReleased(this, new MonoGameMouseEventArgs(
current.X,
current.Y,
gameTime.TotalGameTime,
_previous,
current,
MouseButton.Left));
}

if (current.MiddleButton == ButtonState.Released
&& _previous.MiddleButton == ButtonState.Pressed)
{
OnButtonReleased(this, new MonoGameMouseEventArgs(
current.X,
current.Y,
gameTime.TotalGameTime,
_previous,
current,
MouseButton.Middle));
}

if (current.RightButton == ButtonState.Released
&& _previous.RightButton == ButtonState.Pressed)
{
OnButtonReleased(this, new MonoGameMouseEventArgs(
current.X,
current.Y,
gameTime.TotalGameTime,
_previous,
current,
MouseButton.Right));
}

if (current.XButton1 == ButtonState.Released
&& _previous.XButton1 == ButtonState.Pressed)
{
OnButtonReleased(this, new MonoGameMouseEventArgs(
current.X,
current.Y,
gameTime.TotalGameTime,
_previous,
current,
MouseButton.XButton1));
}

if (current.XButton2 == ButtonState.Released
&& _previous.XButton2 == ButtonState.Pressed)
{
OnButtonReleased(this, new MonoGameMouseEventArgs(
current.X,
current.Y,
gameTime.TotalGameTime,
_previous,
current,
MouseButton.XButton2));
}

// Check for any sort of mouse movement. If a button is down, it's a drag,
// otherwise it's a move.
if (_previous.X != current.X || _previous.Y != current.Y)
{
OnMouseMoved(this, new MonoGameMouseEventArgs(
current.X,
current.Y,
gameTime.TotalGameTime,
_previous,
current));
}

// Handle mouse wheel events.
if (_previous.ScrollWheelValue != current.ScrollWheelValue)
{
var value = current.ScrollWheelValue / 120;
var delta = (current.ScrollWheelValue - _previous.ScrollWheelValue) / 120;
OnMouseWheelMoved(this, new MonoGameMouseEventArgs(
current.X,
current.Y,
gameTime.TotalGameTime,
_previous,
current,
MouseButton.None,
value,
delta));
}

_previous = current;
}

这里仍旧是在Update中遍历Mouse的状态, 依旧是轮询的方式. 只是在使用方式了采取了一种类似event-driven的写法, 不是真正的event-driven吧, 不知道是不是我的理解错了.

那为什么在XNA中是轮询(Polling), 而不是事件驱动(Event Driven)? 其他引擎呢? 像Flash默认是事件监听来响应输入的. 后面的笔记会试图回答这些问题.